/*
 * $Id: time_utime2.c,v 1.3 2005-08-06 11:52:14 bacon Exp $
 */

/*
 * mktime.c
 * Original Author:	G. Haley
 *
 * Converts the broken-down time, expressed as local time, in the structure
 * pointed to by tim_p into a calendar time value. The original values of the
 * tm_wday and tm_yday fields of the structure are ignored, and the original
 * values of the other fields have no restrictions. On successful completion
 * the fields of the structure are set to represent the specified calendar
 * time. Returns the specified calendar time. If the calendar time can not be
 * represented, returns the value (time_t) -1.
 */

#include <xp/bas/time.h>

static int __month_days[12] = 
{
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

static int __days_before_month[12] =
{
	0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};

#define IS_LEAPYEAR(y) \
	(((y) % 4) == 0 && (((y) % 100) != 0 || \
	(((y)+XP_UTIME_BASE_YEAR) % 400) == 0))
#define YEAR_DAYS(year) (IS_LEAPYEAR(year) ? 366 : 365)

struct __utime_t
{
	xp_time_t msec;
	xp_time_t sec;
	xp_time_t min;
	xp_time_t hour;
	xp_time_t day;
	xp_time_t mon;
	xp_time_t year;
};

typedef struct __utime_t __utime_t;

static void __validate_utime (__utime_t* tim_p)
{
	int days_in_feb = 28;
#define MONTH_DAYS(x) ((x == 1) ? days_in_feb : __month_days[x])

	if (tim_p->msec < 0 || tim_p->msec >= XP_MSEC_IN_SEC) {
		tim_p->sec += tim_p->msec / XP_MSEC_IN_SEC;
		tim_p->msec = tim_p->msec % XP_MSEC_IN_SEC;	

		if (tim_p->msec < 0) {
			tim_p->msec += XP_MSEC_IN_SEC;
			tim_p->sec--;
		}
	}

	if (tim_p->sec < 0 || tim_p->sec >= XP_SEC_IN_MIN) {
		tim_p->min += tim_p->sec / XP_SEC_IN_MIN;
		tim_p->sec = tim_p->sec % XP_SEC_IN_MIN;

		if (tim_p->sec < 0) {
			tim_p->sec += XP_SEC_IN_MIN;
			tim_p->min--;
		}
	}

	if (tim_p->min < 0 || tim_p->min >= XP_MIN_IN_HOUR) {
		tim_p->hour += tim_p->min / XP_MIN_IN_HOUR;
		tim_p->min = tim_p->min % XP_MIN_IN_HOUR;

		if (tim_p->min < 0) {
			tim_p->min += XP_MIN_IN_HOUR;
			tim_p->hour--;
		}
	}

	if (tim_p->hour < 0 || tim_p->hour >= XP_HOUR_IN_DAY) {
		tim_p->day += tim_p->hour / XP_HOUR_IN_DAY;
		tim_p->hour = tim_p->hour % XP_HOUR_IN_DAY;

		if (tim_p->hour < 0) {
			tim_p->hour += XP_HOUR_IN_DAY;
			tim_p->day--;
		}
	}

	if (tim_p->mon >= XP_MON_IN_YEAR) {
		tim_p->year += tim_p->mon / XP_MON_IN_YEAR;
		tim_p->mon = tim_p->mon % XP_MON_IN_YEAR;

		if (tim_p->mon < 0) {
			tim_p->mon += XP_MON_IN_YEAR;
			tim_p->year--;
		}
	}

	if (YEAR_DAYS(tim_p->year) == 366) days_in_feb = 29;

	if (tim_p->day <= 0) {
		while (tim_p->day <= 0) {
			if (--tim_p->mon == -1) {
				tim_p->year--;
				tim_p->mon = XP_MON_IN_YEAR - 1;
				days_in_feb = ((YEAR_DAYS(tim_p->year) == 366) ? 29 : 28);
			}
			tim_p->day += MONTH_DAYS(tim_p->mon);
		}
	}
	else {
		while (tim_p->day > MONTH_DAYS (tim_p->mon)) {
			tim_p->day -= MONTH_DAYS (tim_p->mon);
			if (++tim_p->mon == XP_MON_IN_YEAR) {
				tim_p->year++;
				tim_p->mon = 0;
				days_in_feb = ((YEAR_DAYS(tim_p->year) == 366) ? 29 : 28);
			}
		}
	}
}

int xp_utime_to_time (xp_utime_t* ut, xp_time_t* t)
{
	__utime_t tim_p;
	xp_time_t tim = 0;
	xp_time_t days = 0;
	xp_time_t year;
	xp_time_t wday, yday;

	tim_p.msec = ut->msec;
	tim_p.sec  = ut->sec;
	tim_p.min  = ut->min;
	tim_p.hour = ut->hour;
	tim_p.day  = ut->day;
	tim_p.mon  = ut->mon;
	tim_p.year = ut->year;

	/* validate structure */
	__validate_utime (&tim_p);

	/* compute hours, minutes, seconds */
	tim += tim_p.msec + (tim_p.sec * XP_MSEC_IN_SEC) +
		(tim_p.min * XP_MSEC_IN_MIN) + (tim_p.hour * XP_MSEC_IN_HOUR);

	/* compute days in year */
	days += tim_p.day - 1;
	days += __days_before_month[tim_p.mon];
	if (tim_p.mon > 1 && YEAR_DAYS(tim_p.year) == 366) days++;

	/* compute day of the year */
	yday = days;

	if (tim_p.year > 10000 || tim_p.year < -10000) return -1;

	/* compute days in other years */
	if (tim_p.year > 70) {
		for (year = 70; year < tim_p.year; year++)
			days += YEAR_DAYS(year);
	}
	else if (tim_p.year < 70) {
		for (year = 69; year > tim_p.year; year--)
			days -= YEAR_DAYS(year);
		days -= YEAR_DAYS(year);
	}

	/* compute day of the week */
	if ((wday = (days + 4) % XP_DAY_IN_WEEK) < 0) wday += XP_DAY_IN_WEEK;

	/* compute total seconds */
	tim += (days * XP_MSEC_IN_DAY);

	*t = tim;
	ut->wday = wday;
	ut->yday = yday;

	return 0;
}

